#include "tcp_client.h"
#include <boost/thread.hpp>
#include <string>

namespace auroral { namespace net {

//************************************ Class TcpClient *********************************

TcpClient::TcpClient(boost::asio::io_service& io_service,
	const std::string& server, int16_t port)
	:socket_(io_service),
	writting(false),
	strand_(io_service),
	resolver_(io_service),
	server_(server),
	port_(port)

{

}

TcpClient::~TcpClient()
{

};


bool TcpClient::active()
{
	std::stringstream ss;
	// convert the port to string.
	ss << port_;
	tcp::resolver::query query(server_, ss.str(), tcp::resolver::query::numeric_service);
	resolver_.async_resolve(query,
		boost::bind(&TcpClient::handle_resolve, this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::iterator));
	return true;
}

void TcpClient::async_write(const std::string &message)
{
	strand_.dispatch(boost::bind(&TcpClient::push_write_queue, this, message));
}

void TcpClient::push_write_queue(const std::string message)
{
	// this method should be run in @strand_.
	messages_.push(message);
	if(!writting && socket_.is_open())
	{
		writting = true;
		boost::asio::async_write(socket_, boost::asio::buffer(messages_.front()),
			strand_.wrap(
			boost::bind(&TcpClient::handle_write, this,
			boost::asio::placeholders::error,
			boost::asio::placeholders::bytes_transferred)));
	}
	// if the socket is not open, handle_connect() should run async_write instead.
}

void TcpClient::handle_read(const boost::system::error_code& err) 
{
	if (!err)
	{
		process_read();
		boost::asio::async_read_until(socket_, response_, "\r\n",
			boost::bind(&TcpClient::handle_read, this,
			boost::asio::placeholders::error));
	}
	else
	{
		// you may need to overloaded this function to do something difference.
		std::cerr << "Error: " << err.message() << "\n";
		std::cerr << "TcpClient exit" << "\n";		
	}
}

void TcpClient::handle_write(const boost::system::error_code& err/*error*/,
								 size_t /*bytes_transferred*/)
{
	if (!err)
	{
		messages_.pop();
		if (messages_.empty())
		{
			writting = false;
		}
		else
		{
			boost::asio::async_write(socket_, boost::asio::buffer(messages_.front()),
				strand_.wrap(
				boost::bind(&TcpClient::handle_write, this,
				boost::asio::placeholders::error,
				boost::asio::placeholders::bytes_transferred)));
		}
	}
	else
	{
		writting = false;
		//std::cerr << "Error: " << err.message() << "\n";
	}
}

void TcpClient::handle_resolve(const boost::system::error_code& err,
	tcp::resolver::iterator endpoint_iterator)
{
	if (!err)
	{
		// Attempt a connection to the first endpoint in the list. Each endpoint
		// will be tried until we successfully establish a connection.
		tcp::endpoint endpoint = *endpoint_iterator;
		socket_.async_connect(endpoint,
			strand_.wrap(
			boost::bind(&TcpClient::handle_connect, this,
			boost::asio::placeholders::error, ++endpoint_iterator)));
	}
	else
	{
		std::cerr << "Error: " << err.message() << "\n";
	}
}

void TcpClient::handle_connect(const boost::system::error_code& err,
	tcp::resolver::iterator endpoint_iterator)
{
	if (!err)
	{
		// The connection was successful. Send the request.
		boost::asio::async_read_until(socket_, response_, "\r\n",
			boost::bind(&TcpClient::handle_read, this,
			boost::asio::placeholders::error));
		if(!writting && !messages_.empty())
		{
			writting = true;
			boost::asio::async_write(socket_, boost::asio::buffer(messages_.front()),
				strand_.wrap(
				boost::bind(&TcpClient::handle_write, this,
				boost::asio::placeholders::error,
				boost::asio::placeholders::bytes_transferred)));
		}
	}
	else if (endpoint_iterator != tcp::resolver::iterator())
	{
		// The connection failed. Try the next endpoint in the list.
		socket_.close();
		tcp::endpoint endpoint = *endpoint_iterator;
		socket_.async_connect(endpoint,
			boost::bind(&TcpClient::handle_connect, this,
			boost::asio::placeholders::error, ++endpoint_iterator));
	}
	else
	{
		std::cerr << "Error: " << err.message() << "\n";
	}
}

} }; //namespace auroral::net